/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package com.python.pydev.analysis.visitors;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.python.pydev.core.FullRepIterable;
import org.python.pydev.core.ICompletionCache;
import org.python.pydev.core.IDefinition;
import org.python.pydev.core.IModule;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.IToken;
import org.python.pydev.core.OrderedSet;
import org.python.pydev.core.docutils.StringUtils;
import org.python.pydev.core.log.Log;
import org.python.pydev.core.structure.CompletionRecursionException;
import org.python.pydev.core.structure.FastStack;
import org.python.pydev.editor.codecompletion.revisited.CompletionStateFactory;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceToken;
import org.python.pydev.editor.codecompletion.revisited.visitors.Definition;
import org.python.pydev.editor.refactoring.PyRefactoringFindDefinition;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.Assign;
import org.python.pydev.parser.jython.ast.Call;
import org.python.pydev.parser.jython.ast.ClassDef;
import org.python.pydev.parser.jython.ast.FunctionDef;
import org.python.pydev.parser.jython.ast.NameTok;
import org.python.pydev.parser.jython.ast.decoratorsType;
import org.python.pydev.parser.jython.ast.exprType;
import org.python.pydev.parser.jython.ast.stmtType;
import org.python.pydev.parser.visitors.NodeUtils;
import com.python.pydev.analysis.visitors.OccurrencesVisitor.TokenFoundStructure;
/**
* @author Fabio
*
*/
public final class ArgumentsChecker {
private final IModule current;
private final IPythonNature nature;
private final ICompletionCache completionCache;
private final MessagesManager messagesManager;
public ArgumentsChecker(OccurrencesVisitor occurrencesVisitor) {
this.current = occurrencesVisitor.current;
this.nature = occurrencesVisitor.nature;
this.completionCache = occurrencesVisitor.completionCache;
this.messagesManager = occurrencesVisitor.messagesManager;
}
private static final int NO_STATIC_NOR_CLASSMETHOD = 0;
private static final int CLASSMETHOD = 1;
private static final int STATICMETHOD = 2;
private int isStaticOrClassMethod(FunctionDef functionDefinitionReferenced) {
if (functionDefinitionReferenced.decs != null) {
for (decoratorsType dec : functionDefinitionReferenced.decs) {
if (dec != null) {
String rep = NodeUtils.getRepresentationString(dec.func);
if (rep != null) {
if (rep.equals("staticmethod")) {
return STATICMETHOD;
} else if (rep.equals("classmethod")) {
return CLASSMETHOD;
}
}
}
}
}
//If it got here, there may still be an assign to it...
if (functionDefinitionReferenced.parent instanceof ClassDef) {
ClassDef classDef = (ClassDef) functionDefinitionReferenced.parent;
stmtType[] body = classDef.body;
if (body != null) {
int len = body.length;
String funcName = ((NameTok) functionDefinitionReferenced.name).id;
for (int i = 0; i < len; i++) {
if (body[i] instanceof Assign) {
Assign assign = (Assign) body[i];
if (assign.targets == null) {
continue;
}
//we're looking for xxx = staticmethod(xxx)
if (assign.targets.length == 1) {
exprType t = assign.targets[0];
String rep = NodeUtils.getRepresentationString(t);
if (rep == null) {
continue;
}
}
exprType expr = assign.value;
if (expr instanceof Call) {
Call call = (Call) expr;
if (call.args.length == 1) {
String argRep = NodeUtils.getRepresentationString(call.args[0]);
if (argRep != null && argRep.equals(funcName)) {
String funcCall = NodeUtils.getRepresentationString(call.func);
if ("staticmethod".equals(funcCall)) {
//ok, finally... it is a staticmethod after all...
return STATICMETHOD;
}
if ("classmethod".equals(funcCall)) {
return CLASSMETHOD;
}
}
}
}
}
}
}
}
return NO_STATIC_NOR_CLASSMETHOD;
}
@SuppressWarnings("unchecked")
/*default*/void checkAttrFound(Call callNode, TokenFoundStructure found) throws Exception,
CompletionRecursionException {
FunctionDef functionDefinitionReferenced;
SourceToken nameToken;
boolean callingBoundMethod;
if (found != null && found.defined && found.token instanceof SourceToken) {
nameToken = (SourceToken) found.token;
String rep = nameToken.getRepresentation();
ArrayList<IDefinition> definition = new ArrayList<IDefinition>();
SimpleNode ast = nameToken.getAst();
try {
PyRefactoringFindDefinition.findActualDefinition(null, this.current, rep, definition, ast.beginLine,
ast.beginColumn, this.nature, this.completionCache);
} catch (Exception e) {
Log.log(e);
return;
}
for (IDefinition iDefinition : definition) {
Definition d = (Definition) iDefinition;
if (d.ast instanceof FunctionDef) {
functionDefinitionReferenced = (FunctionDef) d.ast;
String withoutLastPart = FullRepIterable.getWithoutLastPart(rep);
Boolean b = valToBounded.get(withoutLastPart);
if (b != null) {
callingBoundMethod = b;
} else {
int count = StringUtils.count(rep, '.');
if (count == 1 && rep.startsWith("self.")) {
FastStack<SimpleNode> scopeStack = d.scope.getScopeStack();
if (scopeStack.size() > 0 && scopeStack.peek() instanceof ClassDef) {
callingBoundMethod = true;
} else {
callingBoundMethod = false;
}
} else {
FastStack<SimpleNode> scopeStack = d.scope.getScopeStack();
if (scopeStack.size() > 1 && scopeStack.peek(1) instanceof ClassDef) {
callingBoundMethod = true;
String withoutLast = FullRepIterable.getWithoutLastPart(rep);
ArrayList<IDefinition> definition2 = new ArrayList<IDefinition>();
PyRefactoringFindDefinition.findActualDefinition(null, this.current, withoutLast,
definition2, -1, -1, this.nature, this.completionCache);
for (IDefinition iDefinition2 : definition2) {
Definition d2 = (Definition) iDefinition2;
if (d2.ast instanceof ClassDef) {
callingBoundMethod = false;
break;
}
}
} else {
callingBoundMethod = false;
}
}
}
analyzeCallAndFunctionMatch(callNode, functionDefinitionReferenced, nameToken, callingBoundMethod);
break;
}
}
}
}
private final Map<ClassDef, SimpleNode> defToConsideredInit = new HashMap<ClassDef, SimpleNode>();
/*default*/void checkNameFound(Call callNode, SourceToken sourceToken) throws Exception {
FunctionDef functionDefinitionReferenced;
boolean callingBoundMethod = false;
SimpleNode ast = sourceToken.getAst();
if (ast instanceof FunctionDef) {
functionDefinitionReferenced = (FunctionDef) ast;
analyzeCallAndFunctionMatch(callNode, functionDefinitionReferenced, sourceToken, callingBoundMethod);
} else if (ast instanceof ClassDef) {
ClassDef classDef = (ClassDef) ast;
SimpleNode initNode = defToConsideredInit.get(classDef);
callingBoundMethod = true;
if (initNode == null) {
String className = ((NameTok) classDef.name).id;
Definition foundDef = sourceToken.getDefinition();
IModule mod = this.current;
if (foundDef != null) {
mod = foundDef.module;
}
SimpleNode n = NodeUtils.getNodeFromPath(classDef, "__init__");
if (n instanceof FunctionDef) {
initNode = n;
} else {
IDefinition[] definition = mod.findDefinition(CompletionStateFactory.getEmptyCompletionState(
className + ".__init__", this.nature, this.completionCache), -1, -1, this.nature);
for (IDefinition iDefinition : definition) {
Definition d = (Definition) iDefinition;
if (d.ast instanceof FunctionDef) {
initNode = d.ast;
defToConsideredInit.put(classDef, initNode);
break;
}
}
}
}
if (initNode instanceof FunctionDef) {
functionDefinitionReferenced = (FunctionDef) initNode;
analyzeCallAndFunctionMatch(callNode, functionDefinitionReferenced, sourceToken, callingBoundMethod);
}
}
}
protected void analyzeCallAndFunctionMatch(Call callNode, FunctionDef functionDefinitionReferenced,
IToken nameToken, boolean callingBoundMethod) throws Exception {
int functionArgsLen = functionDefinitionReferenced.args.args != null ? functionDefinitionReferenced.args.args.length
: 0;
Collection<String> functionRequiredArgs = new OrderedSet<String>(functionArgsLen);
Collection<String> functionOptionalArgs = new OrderedSet<String>(functionArgsLen);
int staticOrClassMethod = isStaticOrClassMethod(functionDefinitionReferenced);
boolean ignoreFirstParameter = callingBoundMethod;
if (staticOrClassMethod != NO_STATIC_NOR_CLASSMETHOD) {
switch (staticOrClassMethod) {
case STATICMETHOD:
ignoreFirstParameter = false;
break;
case CLASSMETHOD:
ignoreFirstParameter = true;
break;
default:
throw new AssertionError("Unexpected condition.");
}
}
for (int i = 0; i < functionArgsLen; i++) {
if (i == 0 && ignoreFirstParameter) {
continue; //Ignore first parameter when calling a bound method.
}
String rep = NodeUtils.getRepresentationString(functionDefinitionReferenced.args.args[i]);
if (functionDefinitionReferenced.args.defaults == null
|| (functionDefinitionReferenced.args.defaults.length > i && functionDefinitionReferenced.args.defaults[i] == null)) {
//it's null, so, it's required
functionRequiredArgs.add(rep);
} else {
//not null: optional with default value
functionOptionalArgs.add(rep);
}
}
exprType[] kwonlyargs = functionDefinitionReferenced.args.kwonlyargs;
Collection<String> functionKeywordOnlyArgs = null;
if (kwonlyargs != null) {
functionKeywordOnlyArgs = new OrderedSet<String>(kwonlyargs.length);
for (exprType exprType : kwonlyargs) {
if (exprType != null) {
functionKeywordOnlyArgs.add(NodeUtils.getRepresentationString(exprType));
}
}
}
int callArgsLen = callNode.args != null ? callNode.args.length : 0;
for (int i = 0; i < callArgsLen; i++) {
if (functionRequiredArgs.size() > 0) {
//Remove first one (no better api in collection...)
Iterator<String> it = functionRequiredArgs.iterator();
it.next();
it.remove();
continue;
} else if (functionOptionalArgs.size() > 0) {
Iterator<String> it = functionOptionalArgs.iterator();
it.next();
it.remove();
continue;
}
//All 'regular' and 'optional' arguments consumed (i.e.: def m1(a, b, c=10)), so, it'll only
//be possible to accept an item that's in *args at this point.
if (functionDefinitionReferenced.args.vararg == null) {
onArgumentsMismatch(nameToken, callNode);
return; //Error reported, so, bail out of function!
}
}
int callKeywordArgsLen = callNode.keywords != null ? callNode.keywords.length : 0;
for (int i = 0; i < callKeywordArgsLen; i++) {
String rep = NodeUtils.getRepresentationString(callNode.keywords[i].arg);
//keyword argument (i.e.: call(a=10)), so, only accepted in kwargs or with some argument with that name.
if (functionRequiredArgs.remove(rep)) {
continue;
} else if (functionOptionalArgs.remove(rep)) {
continue;
} else if (functionKeywordOnlyArgs != null && functionKeywordOnlyArgs.remove(rep)) {
continue;
} else {
//An argument with that name was not found, so, it may only be handled through kwargs at this point!
if (functionDefinitionReferenced.args.kwarg == null) {
onArgumentsMismatch(nameToken, callNode);
return; //Error reported, so, bail out of function!
}
}
}
if (functionRequiredArgs.size() > 0 || (functionKeywordOnlyArgs != null && functionKeywordOnlyArgs.size() > 0)) {
if (callNode.kwargs == null && callNode.starargs == null) {
//Not all required parameters were consumed!
onArgumentsMismatch(nameToken, callNode);
return; //Error reported, so, bail out of function!
}
} else if (functionOptionalArgs.size() > 0) {
} else {
//required and optional size == 0
if (callNode.kwargs != null && functionDefinitionReferenced.args.kwarg == null) {
//We have more things that were not handled
onArgumentsMismatch(nameToken, callNode);
return; //Error reported, so, bail out of function!
}
if (callNode.starargs != null && functionDefinitionReferenced.args.vararg == null) {
//We have more things that were not handled
onArgumentsMismatch(nameToken, callNode);
return; //Error reported, so, bail out of function!
}
}
}
private void onArgumentsMismatch(IToken node, Call callNode) {
this.messagesManager.onArgumentsMismatch(node, callNode);
}
private Map<String, Boolean> valToBounded = new HashMap<String, Boolean>();
public void visitAssign(Assign node) {
boolean bounded = false;
exprType[] targets = node.targets;
if (node.value != null) {
if (node.value instanceof Call) {
bounded = true;
}
}
if (targets != null) {
int len = targets.length;
for (int i = 0; i < len; i++) {
exprType expr = targets[i];
valToBounded.put(NodeUtils.getFullRepresentationString(expr), bounded);
}
}
}
}